{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "# Question Answering on a Knowledge Graph\n",
    "\n",
    "> Starting from version 1.15, `BaseKnowledgeGraph`, `GraphDBKnowledgeGraph`, `InMemoryKnowledgeGraph`, and `Text2SparqlRetriever` are being deprecated and will be removed from Haystack as of version 1.17. For more details about this deprecation, check out [our announcement](https://github.com/deepset-ai/haystack/discussions/4882) on Github. \n",
    "\n",
    "Haystack allows storing and querying knowledge graphs with the help of pre-trained models that translate text queries to SPARQL queries.\n",
    "This tutorial demonstrates how to load an existing knowledge graph into haystack, load a pre-trained retriever, and execute text queries on the knowledge graph.\n",
    "The training of models that translate text queries into SPARQL queries is currently not supported.\n",
    "\n",
    "To start, install the latest release of Haystack with `pip`:"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## Preparing the Colab Environment\n",
    "\n",
    "- [Enable GPU Runtime](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration#enabling-the-gpu-in-colab)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Installing Haystack\n",
    "\n",
    "To start, let's install the latest release of Haystack with `pip`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "\n",
    "pip install --upgrade pip\n",
    "pip install farm-haystack[colab,inmemorygraph]==1.16.1"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Enabling Telemetry \n",
    "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from haystack.telemetry import tutorial_running\n",
    "\n",
    "tutorial_running(10)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Logging\n",
    "\n",
    "We configure how logging messages should be displayed and which log level should be used before importing Haystack.\n",
    "Example log message:\n",
    "INFO - haystack.utils.preprocessing -  Converting data/tutorial1/218_Olenna_Tyrell.txt\n",
    "Default log level in basicConfig is WARNING so the explicit parameter is not necessary but can be changed easily:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import logging\n",
    "\n",
    "logging.basicConfig(format=\"%(levelname)s - %(name)s -  %(message)s\", level=logging.WARNING)\n",
    "logging.getLogger(\"haystack\").setLevel(logging.INFO)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Downloading Knowledge Graph and Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "from haystack.utils import fetch_archive_from_http\n",
    "\n",
    "\n",
    "# Let's first fetch some triples that we want to store in our knowledge graph\n",
    "# Here: exemplary triples from the wizarding world\n",
    "graph_dir = \"data/tutorial10\"\n",
    "s3_url = \"https://fandom-qa.s3-eu-west-1.amazonaws.com/triples_and_config.zip\"\n",
    "fetch_archive_from_http(url=s3_url, output_dir=graph_dir)\n",
    "\n",
    "# Fetch a pre-trained BART model that translates text queries to SPARQL queries\n",
    "model_dir = \"../saved_models/tutorial10_knowledge_graph/\"\n",
    "s3_url = \"https://fandom-qa.s3-eu-west-1.amazonaws.com/saved_models/hp_v3.4.zip\"\n",
    "fetch_archive_from_http(url=s3_url, output_dir=model_dir)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Initialize a knowledge graph and load data"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Currently, Haystack supports two alternative implementations for knowledge graphs:\n",
    "* simple InMemoryKnowledgeGraph (based on RDFLib in-memory store)\n",
    "* GraphDBKnowledgeGraph, which runs on GraphDB."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### InMemoryKnowledgeGraph "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "from haystack.document_stores import InMemoryKnowledgeGraph\n",
    "\n",
    "\n",
    "# Initialize a in memory knowledge graph and use \"tutorial_10_index\" as the name of the index\n",
    "kg = InMemoryKnowledgeGraph(index=\"tutorial_10_index\")\n",
    "\n",
    "# Delete the index as it might have been already created in previous runs\n",
    "kg.delete_index()\n",
    "\n",
    "# Create the index\n",
    "kg.create_index()\n",
    "\n",
    "# Import triples of subject, predicate, and object statements from a ttl file\n",
    "kg.import_from_ttl_file(index=\"tutorial_10_index\", path=Path(graph_dir) / \"triples.ttl\")\n",
    "print(f\"The last triple stored in the knowledge graph is: {kg.get_all_triples()[-1]}\")\n",
    "print(f\"There are {len(kg.get_all_triples())} triples stored in the knowledge graph.\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "tags": []
   },
   "source": [
    "### GraphDBKnowledgeGraph (alternative)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "#### Launching a GraphDB instance\n",
    "\n",
    "Unfortunately, there seems to be no good way to run GraphDB in colab environments.\n",
    "In your local environment, you could start a GraphDB server with docker, feel free to check GraphDB's website for the free version https://www.ontotext.com/products/graphdb/graphdb-free/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# import os\n",
    "# import subprocess\n",
    "# import time\n",
    "\n",
    "# LAUNCH_GRAPHDB = os.environ.get(\"LAUNCH_GRAPHDB\", False)\n",
    "\n",
    "# if LAUNCH_GRAPHDB:\n",
    "#     print(\"Starting GraphDB ...\")\n",
    "#     status = subprocess.run(\n",
    "#         [\n",
    "#             \"docker run -d -p 7200:7200 --name graphdb-instance-tutorial docker-registry.ontotext.com/graphdb-free:9.4.1-adoptopenjdk11\"\n",
    "#         ],\n",
    "#         shell=True,\n",
    "#     )\n",
    "#     if status.returncode:\n",
    "#         raise Exception(\n",
    "#             \"Failed to launch GraphDB. Maybe it is already running or you already have a container with that name that you could start?\"\n",
    "#         )\n",
    "#     time.sleep(5)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "#### Creating a new GraphDB repository (also known as index in haystack's document stores)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# from haystack.document_stores import GraphDBKnowledgeGraph\n",
    "\n",
    "# # Initialize a knowledge graph connected to GraphDB and use \"tutorial_10_index\" as the name of the index\n",
    "# kg = GraphDBKnowledgeGraph(index=\"tutorial_10_index\")\n",
    "\n",
    "# # Delete the index as it might have been already created in previous runs\n",
    "# kg.delete_index()\n",
    "\n",
    "# # Create the index based on a configuration file\n",
    "# kg.create_index(config_path=Path(graph_dir) / \"repo-config.ttl\")\n",
    "\n",
    "# # Import triples of subject, predicate, and object statements from a ttl file\n",
    "# kg.import_from_ttl_file(index=\"tutorial_10_index\", path=Path(graph_dir) / \"triples.ttl\")\n",
    "# print(f\"The last triple stored in the knowledge graph is: {kg.get_all_triples()[-1]}\")\n",
    "# print(f\"There are {len(kg.get_all_triples())} triples stored in the knowledge graph.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# # Define prefixes for names of resources so that we can use shorter resource names in queries\n",
    "# prefixes = \"\"\"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n",
    "# PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n",
    "# PREFIX hp: <https://deepset.ai/harry_potter/>\n",
    "# \"\"\"\n",
    "# kg.prefixes = prefixes"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load the pre-trained retriever"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from haystack.nodes import Text2SparqlRetriever\n",
    "\n",
    "\n",
    "# Load a pre-trained model that translates text queries to SPARQL queries\n",
    "kgqa_retriever = Text2SparqlRetriever(knowledge_graph=kg, model_name_or_path=Path(model_dir) / \"hp_v3.4\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Query Execution\n",
    "\n",
    "We can now ask questions that will be answered by our knowledge graph!\n",
    "One limitation though: our pre-trained model can only generate questions about resources it has seen during training.\n",
    "Otherwise, it cannot translate the name of the resource to the identifier used in the knowledge graph.\n",
    "E.g. \"Harry\" -> \"hp:Harry_potter\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "query = \"In which house is Harry Potter?\"\n",
    "print(f'Translating the text query \"{query}\" to a SPARQL query and executing it on the knowledge graph...')\n",
    "result = kgqa_retriever.retrieve(query=query)\n",
    "print(result)\n",
    "# Correct SPARQL query: select ?a { hp:Harry_potter hp:house ?a . }\n",
    "# Correct answer: Gryffindor\n",
    "\n",
    "print(\"Executing a SPARQL query with prefixed names of resources...\")\n",
    "result = kgqa_retriever._query_kg(\n",
    "    sparql_query=\"select distinct ?sbj where { ?sbj hp:job hp:Keeper_of_keys_and_grounds . }\"\n",
    ")\n",
    "print(result)\n",
    "# Paraphrased question: Who is the keeper of keys and grounds?\n",
    "# Correct answer: Rubeus Hagrid\n",
    "\n",
    "print(\"Executing a SPARQL query with full names of resources...\")\n",
    "result = kgqa_retriever._query_kg(\n",
    "    sparql_query=\"select distinct ?obj where { <https://deepset.ai/harry_potter/Hermione_granger> <https://deepset.ai/harry_potter/patronus> ?obj . }\"\n",
    ")\n",
    "print(result)\n",
    "# Paraphrased question: What is the patronus of Hermione?\n",
    "# Correct answer: Otter"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.9.6 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  },
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
